require "arknights/utils"
-- === Global variables,could be accessed by callbacks ===
staging_start_button = {x=1700, y=960, c=0x016898} -- 蓝色的开始行动按钮
staging_stage_focus_hint = {x=1840, y=280, c=0xc65342} -- 是推荐等级标签的橙色，用于确认是否已经点击关卡
staging_ptsd_toggle = {x=1600, y=885, c=0xffffff} -- 代理指挥checkbox选中后的颜色

-- staging_campaign_hint = { -- 活动在终端的入口提示
--    {x=640, y=125, c=0x7d0022},
--    {x=735, y=125, c=0x7d0022}
-- }
-- staging_campaign_entrance = {x=_data.ScrX/2, y=_data.ScrY/2} -- 活动在终端的入口

-- 新版活动入口
taging_campaign_hint = {
   {x=188, y=897, c=0xcacaca},
   {x=635, y=853, c=0x544343}
}
staging_campaign_entrance = {x=188, y=897}

local _data
local shortcut_button = {x=400, y=55, c=0xD8D8D8} -- home按钮
local shortcut_button_hint = {
   shortcut_button,
   {x=57, y=59, c=0xffffff},
   {x=154, y=88, c=0x313131},
   {x=244, y=54, c=0x30303}
}

local expiring_med = {x=1025, y=555, c=0x8D3D53} -- 时间标签未红色，即将过期的药
local med_confirm = {x=1635, y=865} -- 确认吃药的对勾

local stage_engage_hint = {x=1820, y=60, c=0x7d0000} -- 是移除编队标签，用于确认准备关卡界面
local stage_engage_button = {x=1650, y=780}

local in_battle_hint = {x=760, y=53, c=0xFF7902} -- 关卡界面上方消灭数标签，用于确认关卡尚未结束

-- annihilation special hints
local annih_battle_hint = {x=868, y=60, c=0xff7902}
local annih_focus_hint = {x=1780, y=180, c=0x832228}

-- 入场行为json化
-- cpn_index      : 活动按钮的索引
-- prologue_delay : 点击活动按钮后的延迟
-- entrance_map   : 由关卡名称前缀和入口位置组成，方便处理那些复杂的活动
-- game_table     : 有价值关卡名称+位置
-- need_scroll    : 关卡是否需要扫着看
-- exit_depth     : 需要按几次返回键才能回到作战主界面
-- ui_override    : 关卡内界面的变化

local search_stage_tag_ptr -- works like function signature
local function campaign_entry(sname)
   local cpn_config = _data.campaign_table.current
   local game_prefix = nil-- string.sub(sname, 1, -3)
   for k,_ in pairs(cpn_config.entrance_map) do
      if string.find(sname, k) ~= nil then game_prefix = k end
   end
   mSleep(cpn_config.prologue_delay)
   qcRandTap(cpn_config.entrance_map[game_prefix], 20, 2000)
   if cpn_config.ui_override[game_prefix] ~= nil then
      cpn_config.ui_override[game_prefix]()
   end
   if cpn_config.need_scroll[game_prefix] then
      os.execute("input swipe 1000 100 12000 100 500")
      mSleep(700)
      search_stage_tag_ptr(sname)
   end
   return function ()
      for _ = 1, cpn_config.exit_depth do
         qcKeyBack()
      end
   end
end

local function build_level_table()
   local day = (tonumber(os.date("%w"))+6)%7+1
   if tonumber(os.date("%H")) < 4 then
      day = (day + 5) % 7 + 1
   end
   today_extra = {}

   local function generate_table(src, template, extra_info)
      dst = {}
      for key, sched in pairs(src) do
         if sched[day] > 0 then
            -- generate level tag
            for stage_name, loc in pairs(template) do
               local stage_key = string.gsub(stage_name, "XX", key)
               dst[stage_key] = loc
               -- find today's index from schedule
               today_extra[stage_key] = {i=sched[day], t=extra_info}
            end
         end
      end
      return dst
   end

   _data.supply_level_table = generate_table(_data.supply_schedule, _data.supply_level_table, "supply")
   _data.chip_level_table = generate_table(_data.chip_schedule, _data.chip_level_table, "chip")

   for key, loc in pairs(_data.main_level_table) do
      today_extra[key] = tonumber(string.sub(key, string.find(key, "%d+")))
   end

   if _data.campaign_table.current ~= nil then
      -- 方便使用
      _data.active_campaign =_data.campaign_table.current.game_table
      for key, loc in pairs(_data.active_campaign) do
         today_extra[key] = campaign_entry
      end
   end

   _data.level_table = mergetab(
      _data.main_level_table,
      _data.supply_level_table,
      _data.chip_level_table,
      _data.annihilation_level,
      _data.active_campaign
   )
   return today_extra
end

local function hint_reset_default(quit_action)
   if type(quit_action) == "function" then
      mSleep(1000)
      quit_action()
   end
end

local function reset_stage_slide()
   for i=1,3 do
      os.execute("input swipe 800 100 1800 100 100")
   end
end

-- optimized with smaller template matching area
local function search_stage_tag(op_code)
   if cv == nil then   
      cv = require("libluacv")
   end
   local snap_file = userPath() .. "/res/tmp.png"
   local thresh = 8
   local p_sum, ntrial = 0, 0
   local x1, x2 = _data.level_table[op_code].x-380, _data.level_table[op_code].x+380
   local y1, y2 = _data.level_table[op_code].y-50, _data.level_table[op_code].y+50
   local mx, my = 1000,100
   -- see whether the display is changing
   local variant_sampler = {
      {x=_data.ScrX/4,   y=_data.ScrY/3},
      {x=_data.ScrX/4*3, y=_data.ScrY/3*2},
      {x=_data.ScrX/4,   y=_data.ScrY/3*2},
      {x=_data.ScrX/4*3, y=_data.ScrY/3},
      {x=_data.ScrX/2,   y=_data.ScrY/2},
   }
   local unchanged_cnt = 0
   for _,t in ipairs(variant_sampler) do
      t.c = getColorRGB(t.x, t.y)
   end
   touchDown(mx, my)
   while true do
      snapshot(snap_file, x1, y1, x2, y2)
      local p,x,y = cv.cvMatchTemplate(snap_file, userPath().."/res/optags/op-"..op_code..".png")
      -- toast(string.format( "found stage @ [%d, %d] with %f", x, y, p ), 1)
      if p_sum ~= 0 and p < p_sum / ntrial / thresh then
         randTap(x+x1+5,y+y1+5,5,50)
         mSleep(500)
         touchUp(mx, my)
         mSleep(1000)
         return
      elseif p_sum ~= 0 and p / thresh >= p_sum / ntrial then
         p_sum = p
         ntrial = 1
         -- os.execute("input swipe 800 100 1000 100 200")
         mx, my = drag(mx, my, 100, 0, 10)
      else
         p_sum = p_sum + p
         ntrial = ntrial + 1
         if ntrial >= 40 then
            touchUp(mx, my)
            mx = 1000
            reset_stage_slide()
            p_sum = p_sum / ntrial
            ntrial = 1
            touchDown(mx, my)
         else
            -- os.execute("input swipe 1000 100 900 100 200")
            mx, my = drag(mx, my, -100, 0, 10)
            local changed = false
            for _,t in ipairs(variant_sampler) do
               local next_color = getColorRGB(t.x, t.y)
               changed = changed or (next_color ~= t.c)
               t.c = next_color
            end
            if changed then
               unchanged_cnt = 0
            else
               unchanged_cnt = unchanged_cnt + 1
            end
            if unchanged_cnt > 5 then
               unchanged_cnt = 0
               touchUp(mx, my)
               mx = 1000
               reset_stage_slide()
               p_sum = p_sum / ntrial
               ntrial = 1
               touchDown(mx, my)
            end
         end
      end
   end
end
search_stage_tag_ptr = search_stage_tag

local function reset_chapter_slide()
   for i=1,3 do
      os.execute("input swipe 1000 100 0 100 100")
   end
   mSleep(500)
end

local function main_get_current_chap()
   local chap_tag = {{x=1590, y=1005},{x=1665,y=1050}}
   local chap_no = nil
   while chap_no == nil do 
      local text = string.gsub(baiduOcr(chap_tag[1].x, chap_tag[1].y, chap_tag[2].x, chap_tag[2].y), "O", "0")
      if text[1] == '0' then
         text = string.sub(text,2,-1)
      end
      chap_no = tonumber(text)
   end
   return chap_no
end

-- 进入对应关卡列表的一系列操作
local function goto_stage_page(stage_name)
   -- 参数dyn_act作为额外信息，刷活动图时，dyn_act应该是一个包含进入关卡逻辑的函数
   function __goto_by_code(grp_idx, dyn_act)
      -- new layout:
      -- : margin_left:0.069, interval:0.143 * 6, margin_right:0.073
      local stage_group_margin_left = 0.06*_data.ScrX
      local stage_group_margin_right = 0.064*_data.ScrX
      local stage_group_seperator = 0.125*_data.ScrX

      local stage_group = {x=stage_group_margin_left+stage_group_seperator*(grp_idx-1), y=0.9*_data.ScrY}
      -- toast("locating \""..stage_name.."\"")
      qcRandTap(stage_group, 20, 1500)

      if grp_idx == 2 then 
         -- dyn_act = 关卡章节
         local chapter_margin_left = 98 -- 大章节签
         local prev_chap_margin_top = 200 -- chap selector in main stage list
         local next_chap_margin_top = 776

         local prev_chap = {x=chapter_margin_left, y=prev_chap_margin_top}
         local next_chap = {x=chapter_margin_left, y=next_chap_margin_top}
         local left_most_ep = { x=450+_data.ScrX/2, y=2+_data.ScrY*0.5 }
         local prev_ep = {x=1475, y=1000, c=0x313131} -- chap selector in main stage list

         local chap_no = _data.main_level_chapter[1]
         for _ =2,#_data.main_level_chapter do
            qcRandTap(prev_chap, 10, 700)
         end
         for _, ep in ipairs(_data.main_level_chapter) do
            if dyn_act > ep then
               qcRandTap(next_chap, 10, 700)
               chap_no = ep
            end
         end
         
         swipe({x=_data.ScrX*0.9, y=_data.ScrY}, {x=-_data.ScrX, y=_data.ScrY}, 700)
         mySleep(500)
         qcRandTap(left_most_ep, 50, 1000)

         while chap_no ~= dyn_act do
            qcRandTap(prev_ep, 10, 1500)
            chap_no = chap_no - 1
         end
         reset_stage_slide()
      elseif grp_idx == 5 then
         local supply_type = {x=247+(dyn_act.i-1)*312, y=_data.ScrY/2}
         local chip_type = {x=_data.ScrX-247+(dyn_act.i-4)*312, y=_data.ScrY/2}
         if dyn_act.t == 'chip' then
            -- os.execute("input swipe 1000 500 100 500 300")
            swipe({x=1000, y=500}, {x=100, y=500}, 300)
            mSleep(500)
            qcRandTap(chip_type, 30, 1500)
         else
            -- os.execute("input swipe 100 500 1000 500 300")
            swipe({x=100, y=500}, {x=1000, y=500}, 300)
            mSleep(500)
            qcRandTap(supply_type, 30, 1500)
         end
      elseif grp_idx == 6 then
         local annihilation_entrance = {x=438, y=790, c=0x313131}
         local annihilation_hint = {x=80, y=990, c=0xeb2d35}
         local annihilation_hint_blur = {x=80, y=990, c=0x701b1c}
         waitForColor(annihilation_entrance, 500)
         waitForColor(annihilation_hint, 500, 
            function ()
               qcRandTap(annihilation_entrance, 10, 700)
            end)
         qcRandTap({x=960, y=540}, 20, 200)
         waitForColor(annihilation_hint_blur, 300, 
            function ()
               qcRandTap({x=1750, y=1000}, 10, 800)
            end
         )
      else
         -- waitForAllColors(staging_campaign_hint, 500)
         mSleep(1000) -- icon is masked with fx
         qcRandTap(staging_campaign_entrance)
         return dyn_act(stage_name) -- 返回退出动作
      end
   end

   local retval = nil
   if hasKey(_data.main_level_table, stage_name) then
      retval = __goto_by_code(2, today_extra[stage_name])
      search_stage_tag(stage_name)
   elseif hasKey(_data.supply_level_table, stage_name) then
      retval = __goto_by_code(5, today_extra[stage_name])
   elseif hasKey(_data.chip_level_table, stage_name) then
      retval = __goto_by_code(5, today_extra[stage_name]) 
   elseif hasKey(_data.active_campaign, stage_name) then
      retval = __goto_by_code(_data.campaign_table.current.cpn_index, today_extra[stage_name])
      -- search_stage_tag(stage_name)
   elseif hasKey(_data.annihilation_level, stage_name) then
      staging_stage_focus_hint = annih_focus_hint
      in_battle_hint = annih_battle_hint
      retval = __goto_by_code(6)
   else
      dialog("failed to find level \"" .. stage_name .."\"", 3)
      log("failed to find level \"" .. stage_name .."\"", "staging")
   end
   return retval
end


local function get_level_str(t)
   local lvs = {}
   for n, _ in pairs(t) do
      table.insert(lvs, n)
   end
   table.sort(lvs)
   local ret = table.concat(lvs, ",")
   return ret-- string.sub(ret, 2, -1)
end

local function handle_restore_sanity(num_med)
   local ret = nil
   if not qcIsColor(stage_engage_hint) then
      --- no sanity, eat med
      if ((qcIsColor(expiring_med) and num_med == -1) or num_med > 0) then
         -- confirm medicine
         waitForColor(staging_stage_focus_hint, 500, 
            function ()
               qcRandTap(med_confirm, 10, 600)
            end
         )
         -- randTap(staging_start_button.x,staging_start_button.y,10,80)
         qcRandTap(staging_start_button,10,800)
         ret = true
         log("[DATE][arknights_auto_staging]: med taken, "..tostring(num_med-1).." to take", "staging")
         toast(string.format("还能吃 %d 个药", num_med-1), 1)
      else
         toast("理智不足")
         mSleep(500)
      end
   else
      ret = false
   end
   return ret
end

-- give the StageTag location for checking finish
local function start_stage(loc,eat)
   while not qcIsColor(staging_start_button) do
      qcRandTap(loc, 7, 1200)
   end

   if not qcIsColor(staging_ptsd_toggle) then
      qcRandTap(staging_ptsd_toggle, 5, 500)
   end
   -- randTap(staging_start_button.x, staging_start_button.y, 10,80)
   waitWhileColor(staging_stage_focus_hint, 350, 
      function () 
         qcRandTap(staging_start_button, 10, 1000)
      end
   )
   waitWhileBlackScreen(500)
   local ret = handle_restore_sanity(eat)
   
   if ret ~= nil then
      waitWhileColor(stage_engage_hint, 350, function()
            qcRandTap(stage_engage_button, 20)
         end
      )
   else
      qcKeyBack(500)
   end
   
   return ret
end


local function do_stage(stage_name, eat)
   local eaten = start_stage(_data.level_table[stage_name], eat)
   if eaten == nil then
      mSleep(500)
      qcKeyBack()
      return false, eat
   end
   log("[DATE][arknights_auto_staging] stage \"" .. stage_name .."\" started", "staging")
   mSleep(30*1000);
   while qcIsColor(in_battle_hint) do
      mSleep(3000)
      toast("=== in battle ===")
   end
   waitForAllColors(shortcut_button_hint, 100,
      function()
         qcRandTap({x=980, y=540}, 200, 500)
         mSleep(1000)
      end
   )
   if eat > 0 and eaten then 
      eat = eat - 1 
   end
   mSleep(1500)
   return true, eat
end


local function do_main_1_11()
   local stage_name = "1-11"
   local story_hint = {
      {x=600, y=953, c=0xffffff},
      {x=1051, y=954, c=0xffffff},
      {x=1042, y=957, c=0xffffff}
   }
   local manual_sim = require("arknights/arknights_manual_sim")

   start_stage(_data.level_table[stage_name],0)
   log("[DATE][arknights_auto_staging] stage \"" .. stage_name .."\" started", "staging")
   waitForAllColors(story_hint, 900)
   manual_sim.run_1_11()
   mSleep(10*1000);
   while qcIsColor(in_battle_hint) do
      mSleep(3000)
      toast("=== in battle ===")
   end
   waitForAllColors(shortcut_button_hint, 100,
      function()
         qcRandTap({x=980, y=540}, 200, 500)
         mSleep(1000)
      end
   )
   mSleep(1500)
   return true
end

local function semiauto_run()
   -- main
   local today_extra = build_level_table()
   -- UI Config
   UINew({width=800,height=720});
   UILabel("刷到失智 - 关卡选择")
   UIRadio("rg", get_level_str(_data.level_table));
   UIEdit("eat_str", "吃药数量：", "0")
   UIShow();
   
   mSleep(1000)
   goto_stage_page(rg)
   log(string.format("================= [DATE] script stated | stage \"%s\" with %s meds =================", rg, eat_str), "staging")
   local eat = tonumber(eat_str)
   local cont = true
   while cont do
      if rg == "1-11" then
         do_main_1_11()
         eat = eat - 1
         cont = (eat > 0)
      else
         cont,eat = do_stage(rg, eat)
      end
   end 
end

local auto_stage = {}
auto_stage.run = function(stage_name, expect_run)
   build_level_table()
   if _data.level_table[stage_name] == nil then -- 今日目标关卡未开放
      return -1
   end
   local quit_action = goto_stage_page(stage_name)
   local eat_meds = 0
   if os.date("%w") == "0" then -- 周日嗑药
      eat_meds = -1
   end
   for i=1,expect_run do
      local cont = nil
      if stage_name == "1-11" then
         cont = do_main_1_11()
      else 
         cont,eat_meds = do_stage(stage_name, eat_meds)
      end
      if not cont then
         hint_reset_default(quit_action)
         return i-1
      end
   end
   hint_reset_default(quit_action)
   return expect_run
end

if __name__ == "main" then 
   return auto_stage
else
   init(1)
   _data = require("arknights/data")
   semiauto_run()
end
